package com.atguigu.tingshu.album.service.impl;

import java.util.Date;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.atguigu.tingshu.album.mapper.AlbumStatMapper;
import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
import com.atguigu.tingshu.album.service.VodService;
import com.atguigu.tingshu.common.cache.GuiGuCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.vo.album.AlbumListVo;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.atguigu.tingshu.album.mapper.AlbumAttributeValueMapper;
import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.AlbumStat;
import com.atguigu.tingshu.vo.album.AlbumAttributeValueVo;
import com.atguigu.tingshu.vo.album.AlbumInfoVo;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {


    @Autowired
    private AlbumInfoMapper albumInfoMapper;

    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;

    @Autowired
    private AlbumStatMapper albumStatMapper;


    @Autowired
    private TrackInfoMapper trackInfoMapper;

    @Autowired
    private VodService vodService;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 保存专辑
     *
     * @param albumInfoVo 专辑VO信息 包含专辑标签列表
     * @param userId      用户ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class) // 开启事务，默认发生RuntimeException或Error才会进行事务回滚
    public void saveAlbumInfo(AlbumInfoVo albumInfoVo, Long userId) {
        //开启事务
        //0.TODO 校验用户录入文本是否合法
        String content = albumInfoVo.getAlbumTitle() + albumInfoVo.getAlbumIntro();
        Boolean flag = vodService.scanText(content);
        if (!flag) {
            throw new RuntimeException("文本不合法");
        }

        //1.保存专辑信息
        //1.1 将专辑VO对象转为专辑实体PO对象
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);
        //1.2 为属性赋值：用户ID，面试试听集数，审核状态
        albumInfo.setUserId(userId);
        //专辑状态 0301-审核通过 0302-审核不通过 TODO 待完善
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);
        //免费试听集数暂时设置为5
        albumInfo.setTracksForFree(5);
        //1.3 保存专辑得到专辑ID
        albumInfoMapper.insert(albumInfo);
        Long albumId = albumInfo.getId();

        //2.保存专辑标签关系
        //2.1 从专辑VO入参中获取专辑标签列表
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        //2.2 如果有专辑标签，保存专辑标签关系
        if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
            for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
                //2.2.1 将标签VO转为专辑标签PO对象 关联专辑ID
                AlbumAttributeValue albumAttributeValue = BeanUtil.copyProperties(albumAttributeValueVo, AlbumAttributeValue.class);
                albumAttributeValue.setAlbumId(albumId);
                //2.2.2 保存专辑标签关系
                albumAttributeValueMapper.insert(albumAttributeValue);
            }
        }

        //3.保存专辑统计数值
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_PLAY, 0);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_SUBSCRIBE, 0);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_BUY, 0);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_COMMENT, 0);

        //4.将审核通关专辑ID发送RabbitMQ，通知搜索服务上架索引库文档
        if ("1".equals(albumInfo.getIsOpen())) {
            rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_UPPER, albumId);
        }
    }

    /**
     * 保存专辑统计记录
     *
     * @param albumId  专辑ID
     * @param statType 统计类型
     * @param num      统计数值
     */
    @Override
    public void saveAlbumStat(Long albumId, String statType, int num) {
        AlbumStat albumStat = new AlbumStat();
        albumStat.setAlbumId(albumId);
        albumStat.setStatType(statType);
        albumStat.setStatNum(num);
        albumStatMapper.insert(albumStat);
    }

    /**
     * 分页条件查询当前用户专辑列表
     *
     * @param pageInfo       分页对象
     * @param albumInfoQuery 查询条件：包含：标题、状态、用户ID
     * @return
     */
    @Override
    public Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, AlbumInfoQuery albumInfoQuery) {
        //采用动态SQL
        return albumInfoMapper.getUserAlbumPage(pageInfo, albumInfoQuery);
    }

    /**
     * 删除专辑
     *
     * @param id 专辑ID
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeAlbumInfo(Long id) {
        //1.根据专辑ID查询关联声音数量
        LambdaQueryWrapper<TrackInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TrackInfo::getAlbumId, id);
        Long count = trackInfoMapper.selectCount(queryWrapper);
        //Long count = trackInfoMapper.selectCount(new LambdaQueryWrapper<TrackInfo>().eq(TrackInfo::getAlbumId, id));
        if (count > 0) {
            throw new RuntimeException("专辑下有声音，不能删除");
        }
        //2.删除专辑
        albumInfoMapper.deleteById(id);

        //3.删除专辑标签关系
        albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, id));

        //4.删除专辑统计记录
        albumStatMapper.delete(new LambdaQueryWrapper<AlbumStat>().eq(AlbumStat::getAlbumId, id));

        //5.发送下架专辑ID到RabbitMQ，通知搜索服务删除索引库文档
        rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_LOWER, id);
    }


    /**
     * 根据专辑ID查询专辑信息
     *
     * @param id
     * @return
     */
    @Override
    public AlbumInfo getAlbumInfo(Long id) {
        try {
            //1.优先从分布式缓存Redis获取业务数据
            //1.1 构建缓存业务数据Key 形式=前缀:业务数据ID
            String dataKey = RedisConstant.ALBUM_INFO_PREFIX + id;
            //1.2 尝试从Redis中获取业务数据
            AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(dataKey);
            //1.3 如果命中缓存则直接响应业务数据即可
            if (albumInfo != null) {
                return albumInfo;
            }

            //2.如果缓存未命中，如果此时大量请求访问热点专辑数据，造成数据库压力过载 解决办法：分布式锁
            //2.1 构建分布式锁Key 将锁粒度尽可能细，例如：业务数据Key:锁后缀
            String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
            //2.2 基于Redisson客户端对象创建锁对象 入参为锁名称
            RLock lock = redissonClient.getLock(lockKey);
            //2.3 获取分布式锁
            boolean flag = lock.tryLock();
            if (flag) {
                log.info("获取分布式锁成功，正在查询数据库数据,线程ID：{}", Thread.currentThread().getId());
                try {
                    //3.获取锁成功再执行查询数据库业务，将业务数据放入缓存并且返回，释放锁
                    albumInfo = this.getAlbumInfoFromDB(id);
                    //3.1 生成缓存时间 基础时间+随机时间
                    long ttl = RedisConstant.ALBUM_TIMEOUT + RandomUtil.randomInt(60, 600);
                    //3.2 将查询到数据库中业务数据放入分布式缓存Redis
                    redisTemplate.opsForValue().set(dataKey, albumInfo, ttl, TimeUnit.SECONDS);
                    return albumInfo;
                } finally {
                    //3.3 释放锁
                    lock.unlock();
                }
            } else {
                //4.获取锁失败，自旋
                log.info("获取锁失败，自旋,线程ID：{}", Thread.currentThread().getId());
                try {
                    Thread.sleep(200);
                    this.getAlbumInfo(id);
                } catch (InterruptedException e) {
                    log.error("自旋等待锁异常：{}", e);
                }
            }
        } catch (Exception e) {
            //5.兜底方案，直接查询数据库
            log.error("查询专辑信息异常：{}，执行兜底方案", e);
            return this.getAlbumInfoFromDB(id);
        }
        return null;
    }


    /**
     * 从数据库查询专辑信息
     * 根据专辑ID查询专辑信息（包含专辑关联标签列表）
     *
     * @param id
     * @return
     */
    @Override
    @GuiGuCache(prefix = RedisConstant.ALBUM_INFO_PREFIX)
    public AlbumInfo getAlbumInfoFromDB(Long id) {
        //1.根据专辑ID查询专辑信息
        AlbumInfo albumInfo = albumInfoMapper.selectById(id);
        Assert.notNull(albumInfo, "专辑{}不存在", id);

        //2.查询专辑标签列表
        LambdaQueryWrapper<AlbumAttributeValue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AlbumAttributeValue::getAlbumId, id);
        List<AlbumAttributeValue> albumAttributeValueList = albumAttributeValueMapper.selectList(queryWrapper);
        if (CollectionUtil.isNotEmpty(albumAttributeValueList)) {
            albumInfo.setAlbumAttributeValueVoList(albumAttributeValueList);
        }
        return albumInfo;
    }

    /**
     * 修改专辑信息
     *
     * @param id          专辑ID
     * @param albumInfoVo 修改专辑VO信息
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAlbumInfo(Long id, AlbumInfoVo albumInfoVo) {
        //1.更新专辑表信息
        //1.1 将专辑VO对象转为专辑实体PO对象
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);

        //0.TODO 校验用户录入文本是否合法
        String content = albumInfoVo.getAlbumTitle() + albumInfoVo.getAlbumIntro();
        Boolean flag = vodService.scanText(content);
        if (!flag) {
            albumInfo.setStatus(SystemConstant.ALBUM_STATUS_NO_PASS);
            albumInfoMapper.updateById(albumInfo);
            throw new RuntimeException("文本不合法");
        }

        //1.2 为专辑PO对象设置专辑ID
        albumInfo.setId(id);
        //1.3 更新专辑
        albumInfoMapper.updateById(albumInfo);

        //2.更新专辑标签关系表
        //2.1 先删除专辑标签关系表中的记录
        albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, id));
        //2.2 再新增专辑标签关系记录
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
            for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
                //2.2.1 将标签VO转为专辑标签PO对象 关联专辑ID
                AlbumAttributeValue albumAttributeValue = BeanUtil.copyProperties(albumAttributeValueVo, AlbumAttributeValue.class);
                albumAttributeValue.setAlbumId(id);
                //2.2.2 新增专辑标签关系记录
                albumAttributeValueMapper.insert(albumAttributeValue);
            }
        }
        //3.将审核通关专辑ID发送RabbitMQ，通知搜索服务上架索引库文档
        if ("1".equals(albumInfo.getIsOpen())) {
            rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_UPPER, id);
        }
    }

    /**
     * 查询当前用户专辑列表
     *
     * @param userId
     * @return
     */
    @Override
    public List<AlbumInfo> getUserAllAlbumList(Long userId) {
        //1.封装查询条件对象
        LambdaQueryWrapper<AlbumInfo> queryWrapper = new LambdaQueryWrapper<>();
        //1.1 设置查询条件：用户ID
        queryWrapper.eq(AlbumInfo::getUserId, userId);
        //1.2 设置查询列表：专辑ID、标题
        queryWrapper.select(AlbumInfo::getId, AlbumInfo::getAlbumTitle);
        //1.3 限制响应记录数
        queryWrapper.last("limit 200");
        //1.4 设置排序
        queryWrapper.orderByDesc(AlbumInfo::getId);

        //2. 查询当前用户专辑列表
        return albumInfoMapper.selectList(queryWrapper);
    }

    /**
     * 根据专辑ID查询专辑统计信息
     *
     * @param albumId
     * @return
     */
    @Override
    @GuiGuCache(prefix=RedisConstant.ALBUM_INFO_PREFIX+"stat:")
    public AlbumStatVo getAlbumStatVo(Long albumId) {
        return albumInfoMapper.getAlbumStatVo(albumId);
    }


}
